
#include <stdint.h>
#include <functional>
#include <random>
#include "seg.hpp"

enum EasyxGUI_CTRLID {
    EGI_TEXT, EGI_BUTTON, EGI_COLORSELECTOR, EGI_CHECK
};

typedef struct easyxgui_ctrlproperties
{
    int8_t etype;
    union
    {
        struct __text
        {
            int x;
            int y;
            int w;
            int h;
            TCHAR *s;
            COLORREF bg;
            COLORREF fg;
            COLORREF bd;
        }text,button;
        struct __colorselector
        {
            int x;
            int y;
            int w;
            int h;
            TCHAR *s;
            COLORREF bg;
            COLORREF fg;
            COLORREF bd;
            int x2;
            int y2;
            int w2;
            int h2;
            int h_h;
            bool isHorizon;//false
            uint8_t r;
            uint8_t g;
            uint8_t b;
        }colorselector;
        struct __checker
        {
            int x;
            int y;
            int x2;//dx x->box
            int W;// w of box
            TCHAR *s;
            COLORREF textcolor;
            COLORREF box;
            bool lefttext;
            bool isSelected;
        }checker;
        
    };
    TCHAR *helptext;
    bool autorender; // true
}EasyCtrl;
//int a = sizeof(EasyCtrl);
//#define len 10
//template <int len>

std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<int> rint0to100(0, 100);

inline int randint0to100()
{
    return rint0to100(gen);
}

int RandInt(int _min, int _max)
{
    std::uniform_int_distribution<int> rints(_min, _max);
    return rints(gen);
}
COLORREF RandomColor(uint8_t _min = 0, uint8_t _max = 255)
{
    uint8_t r,g,b;
    r = RandInt(_min, _max);
    g = RandInt(_min, _max);
    b = RandInt(_min, _max);
    return RGB(r,g,b);
}

/*
EasyGUI 240309
*/
class easygui {
    public:
        std::function<int(EasyCtrl*, ExMessage*, int*)> callback_f;
        COLORREF BGC;
        EasyCtrl *ecs;
        int len;
        int idx;
        int last_gid;//上一个改变的id
        // if return, ret 'true' in callback fn.
        easygui(unsigned int len, COLORREF background_color,
            const std::function<int(EasyCtrl*, ExMessage*, int *)> &fn): callback_f(fn) {
            this->idx = 0;
            this->len = len;
            this->BGC = background_color;
            this->ecs = new EasyCtrl[len];
        }
        ~easygui(){
            delete[] this->ecs;
        }
        void pack_text
        (int x,int y,int w,int h,TCHAR*s,COLORREF bg,COLORREF fg,COLORREF bd,TCHAR*helptext=nullptr)
        {
            if (this->idx >= this->len) {
                return;
            }
            this->ecs[this->idx].etype = EGI_TEXT;
            this->ecs[this->idx].autorender = true;
            this->ecs[this->idx].text.x = x;
            this->ecs[this->idx].text.y = y;
            this->ecs[this->idx].text.w = w;
            this->ecs[this->idx].text.h = h;
            this->ecs[this->idx].text.s = s;
            this->ecs[this->idx].text.bg = bg;
            this->ecs[this->idx].text.fg = fg;
            this->ecs[this->idx].text.bd = bd;
            this->ecs[this->idx].helptext = helptext;
            this->idx ++;
        }
        void pack_button
        (int x,int y,int w,int h,TCHAR*s,COLORREF bg,COLORREF fg,COLORREF bd,TCHAR*helptext=nullptr)
        {
            if (this->idx >= this->len) {
                return;
            }
            this->ecs[this->idx].etype = EGI_BUTTON;
            this->ecs[this->idx].autorender = true;
            this->ecs[this->idx].button.x = x;
            this->ecs[this->idx].button.y = y;
            this->ecs[this->idx].button.w = w;
            this->ecs[this->idx].button.h = h;
            this->ecs[this->idx].button.s = s;
            this->ecs[this->idx].button.bg = bg;
            this->ecs[this->idx].button.fg = fg;
            this->ecs[this->idx].button.bd = bd;
            this->ecs[this->idx].helptext = helptext;
            this->idx ++;
        }
        void pack_colorselector
        (int x, int y, int w, int h, TCHAR* s, COLORREF bg, COLORREF fg, COLORREF bd,
            int x2, int y2, int w2, int h2, int h_h, COLORREF value, TCHAR *helptext = nullptr)
        {
            if (this->idx >= this->len) {
                return;
            }
            this->ecs[this->idx].etype = EGI_COLORSELECTOR;
            this->ecs[this->idx].autorender = true;
            this->ecs[this->idx].colorselector.x = x;
            this->ecs[this->idx].colorselector.y = y;
            this->ecs[this->idx].colorselector.w = w;
            this->ecs[this->idx].colorselector.h = h;
            this->ecs[this->idx].colorselector.s = s;
            this->ecs[this->idx].colorselector.bg = bg;
            this->ecs[this->idx].colorselector.fg = fg;
            this->ecs[this->idx].colorselector.bd = bd;
            this->ecs[this->idx].colorselector.x2 = x2;
            this->ecs[this->idx].colorselector.y2 = y2;
            this->ecs[this->idx].colorselector.w2 = w2;
            this->ecs[this->idx].colorselector.h2 = h2;
            this->ecs[this->idx].colorselector.h_h = h_h;
            this->ecs[this->idx].colorselector.isHorizon = true;
            this->ecs[this->idx].helptext = helptext;
            this->ecs[this->idx].colorselector.r = GetRValue(value);
            this->ecs[this->idx].colorselector.g = GetGValue(value);
            this->ecs[this->idx].colorselector.b = GetBValue(value);
            this->idx ++;
        }
        void pack_checker
        (int x, int y, int x2, int W, TCHAR *s, COLORREF textc, COLORREF boxc,
            bool isSelected, TCHAR* helptext=nullptr)
        {
            if (this->idx >= this->len) {
                return;
            }
            this->ecs[this->idx].etype = EGI_CHECK;
            this->ecs[this->idx].autorender = true;
            this->ecs[this->idx].checker.x = x;
            this->ecs[this->idx].checker.y = y;
            this->ecs[this->idx].checker.x2 = x2;
            this->ecs[this->idx].checker.W = W;
            this->ecs[this->idx].checker.s = s;
            this->ecs[this->idx].checker.textcolor = textc;
            this->ecs[this->idx].checker.box = boxc;
            this->ecs[this->idx].checker.isSelected = isSelected;
            this->ecs[this->idx].helptext = helptext;
            this->idx++;
        }
        int get(int gid, EasyCtrl *value)//success return true
        {
            if (gid < 0 || gid >= this->idx) {
                return 0;
            }
            value = &this->ecs[gid];
            return true;
        }
        void mainloop()
        {
            ExMessage msg;
            bool isclicked;
            while (true)
            {
                seg::render::fill(this->BGC);
                isclicked = false;
                this->last_gid = -1;
                getmessage(&msg);
                if (msg.message == WM_LBUTTONDOWN) {
                    isclicked = true;
                }
                for (int i = 0; i < this->idx; i++)
                {
                    // render & event
                    switch (this->ecs[i].etype)
                    {
                    case EGI_TEXT:
                    {
                        this->render_text(i);
                    }
                        break;
                    
                    case EGI_BUTTON:
                    {
                        if (isclicked) {
                            this->render_button(i, &msg);
                        } else {
                            this->render_button(i, nullptr);
                        }
                    } break;
                    
                    case EGI_COLORSELECTOR:
                    {
                        if (isclicked) {
                            this->render_colorchooser(i, &msg);
                        } else {
                            this->render_colorchooser(i, nullptr);
                        }
                    } break;
                    
                    case EGI_CHECK:
                    {
                        if (isclicked) {
                            this->render_checker(i, &msg);
                        } else {
                            this->render_checker(i, nullptr);
                        }
                    }
                    default:
                        break;
                    }
                }
                if (this->last_gid != -1) {
                    FlushBatchDraw();
                    if (this->callback_f(&this->ecs[this->last_gid], &msg, &this->last_gid)) {
                        return;
                    }
                } else {
                    FlushBatchDraw();
                }
            }
            
        }
    public:
    protected:
        void render_text(int gid)
        {
            setlinecolor(this->ecs[gid].text.bd);
            setfillcolor(this->ecs[gid].text.bg);
            //EasyCtrl::__text &e = this->ecs[gid].text;
            #define e this->ecs[gid].text
            fillrectangle(e.x,e.y, e.x + e.w, e.y + e.h);
            settextcolor(this->ecs[gid].text.fg);
            seg::render::text(e.x+e.w/2, e.y + e.h/2, e.s, seg::render::Place::center);
            #undef e
        }
        void render_button(int gid, ExMessage *msg = nullptr)
        {
            setlinecolor(this->ecs[gid].button.bd);
            setfillcolor(this->ecs[gid].button.bg);
            //EasyCtrl::__text &e = this->ecs[gid].button;
            #define e this->ecs[gid].button
            fillrectangle(e.x,e.y, e.x + e.w, e.y + e.h);
            settextcolor(this->ecs[gid].button.fg);
            if (msg != nullptr) {
                if (e.x <= msg->x && msg->x <= e.x + e.w  &&  e.y <= msg->y && msg->y <= e.y + e.h) {
                    this->last_gid = gid;
                }
            }
            seg::render::text(e.x+e.w/2, e.y + e.h/2, e.s, seg::render::Place::center);
            #undef e
        }
        void render_checker(int gid, ExMessage *msg = nullptr)
        {
            settextcolor(this->ecs[gid].checker.textcolor);
            //EasyCtrl::__checker &e = this->ecs[gid].checker;
            #define e this->ecs[gid].checker
            seg::render::text(e.x, e.y, e.s, seg::render::Place::center);
            setlinecolor(e.box);
            setfillcolor(e.box);
            int xx = 0;
            if (e.x2 == 0) {
                xx = e.x + textwidth(e.s)+6;
            } else {
                xx = e.x2;
            }
            const int hw = e.W / 2;
            rectangle(xx-1, e.y-hw-1, xx-1 + e.W + 2, e.y+hw+1);
            rectangle(xx, e.y-hw, xx+e.W, e.y+hw);
            if (msg != nullptr) {
                if (xx <= msg->x && msg->x <= xx + e.W  &&  e.y-hw <= msg->y && msg->y <= e.y + hw) {
                    e.isSelected = !e.isSelected;
                    this->last_gid = gid;
                }
            }
            if (e.isSelected) {
                fillrectangle(xx+3, e.y-hw+3,xx+e.W-4, e.y+hw-4);
            }
            #undef e
        }
        void render_colorchooser(int gid, ExMessage *msg = nullptr)
        {
            settextcolor(this->ecs[gid].colorselector.fg);
            //EasyCtrl::__colorselector &e = this->ecs[gid].colorselector;
            #define e this->ecs[gid].colorselector
            setlinecolor(e.bd);
            setfillcolor(e.bg);
            fillrectangle(e.x, e.y, e.x + e.w, e.y + e.h);
            seg::render::text(e.x + e.w / 2, e.y + 1, e.s, seg::render::Place::top);
            int xx = e.x2;
            int yy = e.y2;
            TCHAR s[20];
            int perx;
            setfillcolor(RED);
            setlinecolor(e.bd);
            if (msg != nullptr) {
                if (msg->x >= xx && msg->x <= xx + e.w2  &&  msg->y >= yy && msg->y <= yy + e.h2) {
                    perx = int(float(msg->x - xx) / e.w2 * 255);
                    e.r = perx;
                    this->last_gid = gid;
                }
            }
            rectangle(xx-1, yy, xx + e.w2 + 1, yy + e.h2);
            setlinecolor(RED);
            fillrectangle(xx, yy+1, xx + e.w2*(e.r/255), yy + e.h2 - 1);
            settextcolor(RED);
            _stprintf(s, _T("%d"), e.r);
            seg::render::text(xx + e.w2 + 10, yy + e.h2/2, s, seg::render::Place::center);
            yy += e.h_h;
            
            setfillcolor(GREEN);
            setlinecolor(e.bd);
            if (msg != nullptr) {
                if (msg->x >= xx && msg->x <= xx + e.w2  &&  msg->y >= yy && msg->y <= yy + e.h2) {
                    perx = int(float(msg->x - xx) / e.w2 * 255);
                    e.g = perx;
                    this->last_gid = gid;
                }
            }
            rectangle(xx-1, yy, xx + e.w2 + 1, yy + e.h2);
            setlinecolor(GREEN);
            fillrectangle(xx, yy+1, xx + e.w2*(e.r/255), yy + e.h2 - 1);
            settextcolor(GREEN);
            _stprintf(s, _T("%d"), e.r);
            seg::render::text(xx + e.w2 + 10, yy + e.h2/2, s, seg::render::Place::center);
            yy += e.h_h;
            
            setfillcolor(BLUE);
            setlinecolor(e.bd);
            if (msg != nullptr) {
                if (msg->x >= xx && msg->x <= xx + e.w2  &&  msg->y >= yy && msg->y <= yy + e.h2) {
                    perx = int(float(msg->x - xx) / e.w2 * 255);
                    e.b = perx;
                    this->last_gid = gid;
                }
            }
            rectangle(xx-1, yy, xx + e.w2 + 1, yy + e.h2);
            setlinecolor(BLUE);
            fillrectangle(xx, yy+1, xx + e.w2*(e.r/255), yy + e.h2 - 1);
            settextcolor(BLUE);
            _stprintf(s, _T("%d"), e.r);
            seg::render::text(xx + e.w2 + 10, yy + e.h2/2, s, seg::render::Place::center);
            
            unsigned long color = RGB(e.r, e.g, e.b);
            _stprintf(s, _T("%lu"), color);
            settextcolor(color);
            seg::render::text(e.x + e.w/2, e.y + e.h -1, s, seg::render::Place::bottom);
            #undef e
        }
};
